var MusicController = {
	get instance () {
		if (!MusicController.$instance) {
			var Klazz = new Class({
				initialize: function() {
					this.$player = COM.createObject("MusicPlayer");
					
					this.$extensions = [];
					this.$player.Filter.split(/(\n)|(;)/).each(function(item){
						if (item.trim()) {
							this.$extensions.push(item.substr(1));
						}
					}.bind(this));
					
					this.$setTimeInfo();
					const updatePeriod = 1000;
					this.$playbackTimer = function () {
						this.$currentTime += updatePeriod / 1000;
						this.fireEvent('onPercentageCompleted', this.$currentTime / this.$totalTime, 0);
					}.bind(this).periodical(updatePeriod); 
					this.$playbackTimer.ticking = false;
					
					this.addEvent('onPercentageCompleted', function (completed) {
						if (completed >= 1 || completed <= 0) {
							this.stop();
							this.fireEvent('onSongFinished', 50);
						}
					}.bind(this));
				},
				
				$setTimeInfo: function (songInfo) {
					this.$currentTime = 0;
					this.$totalTime = songInfo ? songInfo.length.valueOf() : 0;
				},
				
				isPlaying: function () {
					return this.$playbackTimer.ticking;
				},
				
				getSupportedExtensions: function () {
					return this.$extensions;
				},
				
				songSupported: function (filename) {
					var ext = "." + filename.split('.').getLast().toLowerCase();
					return this.$extensions.contains(ext);
				},
				
				play: function (info) {
					var filename = info.path;
					if (this.songSupported(filename)) {
						var stream = this.$player.PlayMusic(filename);
						this.$playbackTimer.ticking = true;
						this.$setTimeInfo(info);
						this.fireEvent('onSongPlaying', filename, 0);
						return true;
					}
					return false;
				},
				
				setPosition: function (percentage) {
					this.$player.PercentageCompletedProp = percentage;
					this.$currentTime = (percentage * this.$totalTime).toInt();
				},
				
				stop: function () {
					if (this.isPlaying()) {
						this.$playbackTimer.ticking = false;
						this.$player.Stop();
						this.$setTimeInfo();
						this.fireEvent('onSongStopping', 0);
					}
				}
				
			});
			Klazz.implement(new Events);
			MusicController.$instance = new Klazz();
		}
		return MusicController.$instance;
	}
};

var ProgressBar = new Class({
	options: {
		fillStyle : "#ABC",
		globalAlpha : 0.5,
		borderSize : 3,
		borderColor : "#000"
	},
	
	initialize: function (canvas, width, height, options) {
		this.setOptions(options);
		this.$ctx = canvas.getContext("2d");
		
		var actualWidth = ($defined(width) ? width : canvas.width);
		var actualHeight =($defined(height) ? height : canvas.height);
		
		this.$width = actualWidth - this.options.borderSize * 2;
		this.$height = actualHeight - this.options.borderSize * 2;
		
		this.$ctx.clearRect(0, 0, actualWidth, actualHeight);
		this.$ctx.lineJoin = "round";
		this.$ctx.lineWidth = this.options.borderSize;
		this.$ctx.strokeStyle = this.options.borderColor;
		this.$ctx.strokeRect(0, 0, actualWidth, actualHeight);
		
		this.$ctx.fillStyle = this.options.fillStyle;
		this.$ctx.globalAlpha = this.options.globalAlpha;
	},
	
	reset: function () {
		this.$ctx.clearRect(this.options.borderSize, this.options.borderSize, this.$width, this.$height);
	},
	
	drawCompleted: function (completed) {
		this.reset();
		this.$ctx.fillRect(this.options.borderSize, this.options.borderSize, this.$width * completed, this.$height);
	}
	
});
ProgressBar.implement(new Options);

var TimeDisplay = new Class({
	initialize: function (text, total) {
		this.text = text;
		this.$total = new Time({second: total});
		this.updateTime(0);
	},
	
	updateText : function () {
		this.text.text = this.$now.toString() + " / " + this.$total.toString();
	},
	
	updateTime : function (nowInSeconds) {
		this.$now = new Time({second: nowInSeconds});
		this.updateText();
	}
});

var HotKeys = new Class({
	initialize: function () {
		this.$hotkeys = {};
	},
	
	setKey: function (name, key) {
		var hotkey = this.$hotkeys[name];
	
		var vals = key.split('+');
		if (vals.length > 4) {
			throw "invalid hot key: " + key;
		}
		var modifier = vals.splice(0, vals.length - 1).join('+');
		var theKey = vals.getLast();
		
		hotkey.key = theKey;
		hotkey.modifier = modifier;
	},
	
	addHotKey: function (name, key, fn) {
		var hotkey = new Element('hotkey', 
			{
				'events' : {
					'KeyDown': fn
				}
			});
		this.$hotkeys[name] = hotkey;			
		this.setKey(name, key);
	},
	
	removeHotKey: function (name) {
		if ($defined(this.$hotkeys[name])) {
			delete this.$hotkeys[name];
		}
	}
});


var SongInfoPopup = Popup.extend({
	options: {
		background: "Resources/growl.png",
		fontFamily: "'Arial'"
	},
	
	initialize: function (options) {
		this.setOptions(options);
		var vgap = 4;
		var hgap = 20;
		
		var frame = new Element('frame', {
			vOffset: 12,
			hOffset: 20,
			'styles': {
				'color': '#FFFFFF',
				'fontSize': '10px'
			}
		});
		
		this.$albumart = new Element('image', {
			vOffset: 3,
			hOffset: 5,
			width: 40,
			height: 40
		});
		this.$artist = new Element('text', {
			vOffset: 0,
			hOffset: this.$albumart.hOffset + this.$albumart.width + hgap,
			height: 12,
			'styles': {
				'fontFamily' : "'"+this.options.fontFamily+"'",
				'fontWeight': 'bold'
			},
			anchorStyle: 'topLeft'
		});
		this.$track = new Element('text', {
			vOffset: this.$artist.vOffset + this.$artist.height + vgap,
			hOffset: this.$albumart.hOffset + this.$albumart.width + hgap,
			height: 12,
			anchorStyle: 'topLeft',
			'styles': {
				'fontFamily' : "'"+this.options.fontFamily+"'"
			}
		});
		this.$album = new Element('text', {
			vOffset: this.$track.vOffset + this.$track.height + vgap,
			hOffset: this.$albumart.hOffset + this.$albumart.width + hgap,
			height: 12,
			'styles': {
				'fontFamily' : "'"+this.options.fontFamily+"'",
				'fontStyle': 'italic'
			},
			anchorStyle: 'topLeft'
		});
		
		frame.adopt(this.$albumart, this.$artist,this.$track,this.$album);
		var bg = new Element('image', {
			src: this.options.background,
			vOffset: 0,
			hOffset: 0
		});
		this.parent(bg.srcWidth, bg.srcHeight, this.options);
		this.$window.adopt(bg, frame);
		frame.orderAbove(bg);
	},
	
	show: function (info) {
		this.$albumart.src = info.album.cover_path;
		this.$artist.text = info.artist.name;
		this.$track.text = info.title;
		this.$album.text = info.album.title;
		this.parent();
	}
	
});

var AudioScrobbler = new Class({
	options: {
		username: preferences.username.value,
		password: preferences.password.value
	},

	initialize: function(options) {
		this.setOptions(options);
		if (!this.options.username || !this.options.password) {
			throw 'please set username and password';
		}
		
		this.handshake();
	},
	
	handshake: function () {
		log('handshaking for user: ' + this.options.username);
		this.$sessionId = null;
		this.$nowPlayingURL = null;
		this.$submitURL = null;
		
		var ts = timestamp();
		var url = new URL('http://post.audioscrobbler.com/');
		url.postData = 'hs=true&p=1.2&c=tst&v=0.1&u='
			+ this.options.username + '&t=' + ts 
			+ '&a=' + hex_md5(hex_md5(this.options.password) + ts);
		url.fetchAsync(function() {
			var response = url.responseData.split('\n');
			switch (response[0]) {
				case 'OK':
					log ('handshake OK!');
					this.$sessionId = response[1];
					this.$nowPlayingURL = new URL(response[2]);
					this.$submitURL = new URL(response[3]);
					break;
				default:
					throw "handshake failed: " + response[0];
			}
		}.bind(this));
	},
	
	nowPlaying: function (songInfo) {
		log("now playing: " + songInfo);
		if (!this.$sessionId || !this.$nowPlayingURL) {
			return;
		}
		this.$song = songInfo;
		this.$time = timestamp();
		
		this.$nowPlayingURL.postData = 
			  's=' + this.$sessionId 
			+ '&a=' + escape(songInfo.artist.name)
			+ '&t=' + escape(songInfo.title)
			+ '&b=' + escape(songInfo.album.title) 
			+ '&l=' + escape(songInfo.length.valueOf())
			+ '&n=' + escape(songInfo.track)
			+ '&m=';
			
		this.$nowPlayingURL.fetchAsync(function() {
			var response = this.$nowPlayingURL.responseData.split('\n');
			switch (response[0]) {
				case 'BADSESSION':
					log('rehandshake');
					this.handshake();
					break;
				case 'OK':
					log('nowplaying OK!');
					break;
			}
		}.bind(this));
	},
	
	submit: function () {
		log("submit: ");
		if (!this.$sessionId || !this.$submitURL || !this.$song) {
			return;
		}
		
		var timePlayed = timestamp() - this.$time;
		var time = this.$time;
		var songInfo = this.$song;
		this.$time = null;
		this.$song = null;
		
		if (timePlayed > 30 
			&& timePlayed > Math.min(240, songInfo.length / 2)) {
			this.$submitURL.postData = 
				  's=' + this.$sessionId 
				+ '&a[0]=' + escape(songInfo.artist.name)
				+ '&t[0]=' + escape(songInfo.title)
				+ '&i[0]=' + time
				+ '&o[0]=P'
				+ '&r[0]=L'
				+ '&b[0]=' + escape(songInfo.album.title) 
				+ '&l[0]=' + escape((songInfo.length.valueOf()).toInt())
				+ '&n[0]=' + escape(songInfo.track)
				+ '&m[0]=';
			log(this.$submitURL);
			
			this.$submitURL.fetchAsync(function() {
				var response = this.$submitURL.responseData.split('\n');
				switch (response[0]) {
					case 'OK':
						log('submittion OK!');
						return;
					case 'BADSESSION':
						log('rehandshake');
						this.handshake();
						break;
					default:
						throw "submission failed: " + response[0];
						break;
				}
			}.bind(this));
		}
	}
});
AudioScrobbler.implement(new Options);

var PlayerUI = new Class({
	
	options: {
		repeat: false, 
		nextTrackKey: 'Alt+Control+V',
		prevTrackKey: 'Alt+Control+Z',
		playTrackKey: 'Alt+Control+X',
		stopTrackKey: 'Alt+Control+C'
	},
	
	initialize: function (options) {
		this.setOptions(options);
		
		this.$song = null;
		this.$player = MusicController.instance;
		this.$playing = false;
		this.$progressbar = new ProgressBar($("playback"));
		$('playback').tracking="rectangle";
		$('playback').addEvent('MouseUp', function (event) {
			if (this.$player.isPlaying()) {
				this.$player.setPosition(event.x / event.target.width);
			}
		}.bind(this));
		this.$timeDisplay = null;
		this.$playlist = new Playlist();
		this.$popup = new SongInfoPopup({fadeDuration: 1000, duration: 5000, fontFamily: preferences.font.value});
		this.$scrobler = new AudioScrobbler();
		
		this.$player.addEvent('onPercentageCompleted', function(completed){
			this.$progressbar.drawCompleted(completed);
			this.$timeDisplay.updateTime(completed * this.$song.length);
		}.bind(this))
		.addEvent('onSongPlaying', function() {
			this.updateButtonState();
			this.$popup.show(this.$song);
			this.$scrobler.nowPlaying(this.$song);
		}.bind(this))
		.addEvent('onSongStopping', function() {
			this.updateButtonState();
			this.$progressbar.reset();
			this.$timeDisplay.updateTime(0);
			this.$scrobler.submit();
		}.bind(this))
		.addEvent('onSongFinished', function() {
			this.$playlist.next();
		}.bind(this));
		
		var playlistWindow = new KonWindow($('playlistWindow'));
		var playerTabs = new KonTab(playlistWindow.mainFrame, 0, {scrollAutoHide: true});
		var playlistFrame = playerTabs.tabs[0];
		playlistFrame.addEvent('DragDrop', function (event) { 
			this.addSongs(event); 
		}.bind(this));
		
		var library = new Playlist();
		var libraryUI = new PlaylistUI(playerTabs.tabs[1], library,
			{
				defaultStyle: {
					fontFamily: "'?'".format(preferences.font.value)
				}
			});
			
		var thread = new CustomAnimation(100, function () {
			const turn = 100;
			if (!$defined(this.notFirstTime)) {
				// -------Cache all entities --------
				Artist.getAllEntities();
				Album.getAllEntities();
				// -----------------------------------
				this.entityCount = Dao.getMaxId(Track.table);
				this.currentId = 1;
				this.notFirstTime = true;
			}
			
			// hack to use animation to simulate a co-operativate thread.
			if (this.currentId < this.entityCount) {
				var tracks = Track.getAllEntities("id >= {0} AND id < {1}".substitute([this.currentId, this.currentId + turn]));
				library.addSongs(tracks);
				this.currentId += turn;
				return true;
			}
			return false;
		});
		animator.start(thread);
		
		var librarySelection = [];
		libraryUI.addEvent('onSelectionChanged', function (selection) {
			librarySelection = selection;
		});
		
		var playlistUI = new PlaylistUI(playlistFrame, this.$playlist, 
			{
				defaultStyle: {
					fontFamily: "'?'".format(preferences.font.value)
				}
			});
		this.$playlist.addEvent('onCurrentSongChanged', function() {
			this.$song = this.$playlist.currentSong;
			if (this.$playing) {
				this.stop();
				this.play();
			}
		}.bind(this))
		.addEvent('onPlaylistFinished', function() {
			var playing = this.$playing;
			this.stop();
			if (this.options.repeat) {
				this.$playlist.reset();
				if (playing) {
					this.play();
				}
			}
		}.bind(this))
		.addEvent('onPlaylistEmpty', function() {
			this.$song = null;
			this.stop();
			$('songinfo').text = "";
			$('timeDisplay').text = '';
		}.bind(this));
		
		var albumArtFrame = playerTabs.tabs[2];
		this.$albumArt = new AlbumArt(albumArtFrame);
		albumArtFrame.addEvent('WidthChanged', function(newval) {
			this.$albumArt.resize(newval, albumArtFrame.height);
		}.bind(this));
		albumArtFrame.addEvent('HeightChanged', function (newval) {
			this.$albumArt.resize(albumArtFrame.width, newval);
		}.bind(this));
		
		$('librarystatus').visible = false;
		var resizeStatus = function () {
			var size = 0;
			playlistWindow.status.getChildren().each(function (el) {
				if (el == $('librarystatus')) {
					return;
				}
				size += el.width;
			});
			$('librarystatus').width = playlistWindow.status.width - size;
			$('librarystatus').flowLayout(Element.Layout.Left);
			playlistWindow.status.flowLayout(Element.Layout.Right);
		};
		resizeStatus();
		
		playerTabs.addEvent('onCurrentTabChanged', function (index) {
			$('librarystatus').visible = (index == 1);
			playlistWindow.status.flowLayout(Element.Layout.Right);
		});
		
		var addBtn = $('add');
		new FadeButton(addBtn);

		$('add').addEvent('Click', function (event) {
			var songs = [];
			librarySelection.each(function (i) {
				var song = library.get(i);
				if (song != null) {
					songs.push(song);
				}
			}); 
			if (songs.length != 0) {
				this.$playlist.addSongs(songs);
			}
		}.bind(this));
		
		var xOffset = 0;
		var yOffset = 0;
		var newSizeX = 0;
		var newSizeY = 0;
		
		var resizeBtn = $('resizeBtn');
		new FadeButton(resizeBtn);
		resizeBtn.addEvent("MouseDown", function (event){
			xOffset = resizeBtn.width - event.x;
			yOffset = resizeBtn.height - event.y;
		})
		.addEvent("MouseDrag", function (event) {
			newSizeX = event.hOffset + xOffset;
			newSizeY = event.vOffset + yOffset;
			playlistWindow.previewResize(newSizeX, newSizeY);
		}).addEvent("MouseUp", function (event) {
			playlistWindow.resize(newSizeX, newSizeY);
			resizeStatus();
		});
		
		var sortbyBtn = $('sortbyBtn');
		new FadeButton(sortbyBtn);
		sortbyBtn.addEvent("Click", function(event) {
			var menuItems = [];
			var sortByFunctions = []; 
			var currentSong;
			var list;
			switch (playerTabs.currentIndex) {
				case 0:
					currentSong = this.$playlist.currentSong;
					list = this.$playlist;
					break;
				case 1:
					currentSong = library.get(0);
					list = library;
					break;
				case 2:
					return;
					break;
			}
			currentSong.fields.keys().each(function (item) {
				sortByFunctions.push(item);
			});
			
			sortByFunctions.each(function (name) {
				var item = $(new MenuItem());
				item.set({
					'title': name,
					'events': {
						'Select': function(event) {
							list.sort(function (a, b) {
								if (a[name] < b[name]) return -1;
								if (a[name] > b[name]) return 1;
								return 0;
							});
						}.bind(this)
					}
				});
				menuItems.push(item);
			}.bind(this));
			if (menuItems.length > 0)
				popupMenu(menuItems, event.hOffset, event.vOffset);
			else 
				alert("no song added.");
		}.bind(this));
		
		$('search').addEvent('KeyUp', function(event) {
			var searchEl = $('search');
			event = new Event(event);
			
			if (event.code == Event.keys.enter) {
				if (!$defined(this.$library)) {
					this.$library = new Playlist();
					var songs = [];
					for (i = 0; i < library.size; i++) {
						songs.push(library.get(i));
					}
					this.$library.addSongs(songs);
				}
			
				searchEl.loseFocus();
				library.empty();
				var songs = [];
				for (i = 0; i < this.$library.size; i++) {
					var song = this.$library.get(i);
					if (searchEl.data == '' 
							|| song.title.test(searchEl.data, 'i') 
							|| song.artist.name.test(searchEl.data, 'i')
							|| song.album.title.test(searchEl.data, 'i')) {
						songs.push(song);
					} 
				}
				library.addSongs(songs);
			}
		});
		
		var coverFlow = new CoverFlow();
		coverFlow.addEvent('onAlbumClicked', function (album) {
			if ($defined(album)) {
				this.$playlist.addSongs(album.tracks);
			}
		}.bind(this));
		
		this.watch('$song', this.$updateCurrent);
		
		this.$hotkeys = new HotKeys();
		this.$hotkeys.addHotKey('nextTrack', this.options.nextTrackKey, this.next.bind(this));
		this.$hotkeys.addHotKey('prevTrack', this.options.prevTrackKey, this.previous.bind(this));
		this.$hotkeys.addHotKey('playTrack', this.options.playTrackKey, this.play.bind(this));
		this.$hotkeys.addHotKey('stopTrack', this.options.stopTrackKey, this.stop.bind(this));
	},
	
	$updateCurrent : function (id, oldval, newval) {
		$('songinfo').text = ($pick(newval, "")).toString();
		this.$albumArt.songInfo = newval;
		this.$timeDisplay = newval ? new TimeDisplay($('timeDisplay'), newval.length) : null;
		if (!this.$timeDisplay) {
			$('timeDisplay').text = '';
		}
		return newval;
	},
	
	updateStop : function () {
		if (this.$player.isPlaying())
			$('stop').src = "Resources/stop.png";
		else 
			$('stop').src = "Resources/oStop.png";
	},
	
	updatePlay : function () {
		if (this.$player.isPlaying())
			$('play').src = "Resources/oplay.png";
		else 
			$('play').src = "Resources/play.png";
	},
	
	updateButtonState: function () {
		this.updatePlay();
		this.updateStop();
	},
	
	play: function () {
		if (this.$song && !this.$player.isPlaying()) {
			this.$player.play(this.$song);
		}
		this.$playing = true;
	},

	stop: function () {
		this.$player.stop();
		this.$playing = false;
	},
	
	previous: function() {
		this.$playlist.prev();
	},
	
	next: function() {
		this.$playlist.next();
	},
	
	addSongs: function(event) {
		if (event.dataType == "filenames") {
			var songs = [];
			var addFiles = function (item) {
				var filename = convertPathToPlatform(item, true);
				if (filesystem.isDirectory(filename)) {
					filesystem.getDirectoryContents(filename).each(function (item, index) {
						addFiles(filename + "\\" + item);
					});
				} else if (this.$player.songSupported(filename)) {
					songs.push(Track.get(filename));
				}
			}.bind(this);
			event.items.each(addFiles);
			
			// every song added is in the same album and has same artist then
			if (songs.every(function (item) {
				return item.album.equals(songs[0].album) 
					&& item.artist.equals(songs[0].artist);
			})) {
				// set the album artist to the song's artist
				songs.each(function (item) {
					item.album.artist = item.artist;
				});
			}
			
			this.$playlist.addSongs(songs);
		}
	},

	eject: function () {
		var choosenFile = chooseFile();
		if (choosenFile != null && this.player.songSupported(choosenFile)) {
			this.$song = Track.get(choosenFile);
		}
	}
	
});
PlayerUI.implement(new Options);

var ui = new PlayerUI({
	repeat: preferences.repeat.value == 1,
	nextTrackKey: preferences.nextTrack.value,
	prevTrackKey: preferences.prevTrack.value,
	playTrackKey: preferences.playTrack.value,
	stopTrackKey: preferences.stopTrack.value
});

$('albumTitleLabel').style.fontFamily = "'"+preferences.font.value+"'";
$('albumArtistLabel').style.fontFamily = "'"+preferences.font.value+"'";
$('songinfo').style.fontFamily = "'"+preferences.font.value+"'";
widget.onPreferencesChanged = function( event ) {
	ui.options.repeat = preferences.repeat.value == 1;
}; 